شماره مستند: ARCH-APIKEY-1405-002
نسخه: ۱.۰
تاریخ تهیه: ۱۲ مهر ۱۴۰۵
تهیه‌کننده: هادی خزاعی اصل
طبقه‌بندی: محرمانه
API KEY MANAGEMENT SYSTEM

طراحی قابلیت مدیریت ApiKey و برنامه‌های کاربردی

بررسی نیازمندی‌ها، معماری و پیاده‌سازی گام به گام

توسعه‌دهنده گرامی،
این مستند به‌عنوان یک طرح جامع فنی، قابلیت ایجاد برنامه‌های کاربردی دارای ApiKey را با پیروی از الگوهای OAuth2 در پلتفرم xDashboard بررسی می‌کند. در این سند، علاوه بر نیازمندی‌های اعلام‌شده، نیازمندی‌های تکمیلی و پیشنهادات بهبودی نیز ارائه شده است تا یک راهکار کامل، امن و مقیاس‌پذیر طراحی گردد.
۱

خلاصه اجرایی و آمار کلیدی

Executive Summary & Key Statistics

سیستم مدیریت ApiKey، یک لایه امنیتی جدید به پلتفرم xDashboard اضافه می‌کند که امکان ایجاد برنامه‌های کاربردی (Applications) با کلیدهای دسترسی موقت را فراهم می‌سازد. این سیستم بر پایه الگوهای OAuth2 Client Credentials طراحی شده و با زیرساخت IdentityServer4 موجود در xIds یکپارچه می‌گردد.

۳
لایه اصلی پیاده‌سازی
۵
Entity جدید
۱۲+
نیازمندی تکمیلی
۴
مرحله پیاده‌سازی
نکته کلیدی: این راهکار با حفظ سازگاری کامل با الگوهای موجود پروژه (مانند XValidationProvider، XException، XPolicies و IXIdentityManager)، قابلیت ادغام بدون اصطکاک با اکوسیستم فعلی را دارد.
۲

بررسی نیازمندی‌های اعلام‌شده

Analysis of Stated Requirements

بر اساس درخواست اعلام‌شده، سه نیازمندی اصلی شناسایی شده‌اند:

# نیازمندی محل پیاده‌سازی الگوی مرجع
۱ ایجاد برنامه کاربردی و تولید ApiKey با مدت انقضای قابل تنظیم xIds OAuth2 Client Credentials
۲ اعتبارسنجی ApiKey در سرویس‌های مرتبط xIdentityService Token Introspection
۳ تعریف دسترسی بر اساس ApiKey در احراز هویت xIds + xApi Policy-based Authorization
💡 تحلیل اولیه: این نیازمندی‌ها در واقع پیاده‌سازی یک OAuth2 Client کامل هستند که در آن هر "برنامه کاربردی" معادل یک Client در IdentityServer4 بوده و ApiKey معادل ClientSecret است. با این حال، برای مدیریت بهتر چرخه حیات، نیاز به یک لایه انتزاعی بالاتر داریم.
۳

نیازمندی‌های تکمیلی و پیشنهادات بهبود

Additional Requirements & Improvement Suggestions

در بررسی دقیق‌تر، نیازمندی‌های زیر شناسایی شدند که در درخواست اولیه ذکر نشده‌اند اما برای یک پیاده‌سازی کامل و امن ضروری هستند:

🔐 الف) نیازمندی‌های امنیتی

🔑

۱. چرخش کلید (Key Rotation)

امکان تولید کلید جدید بدون اختلال در سرویس‌های در حال اجرا، با پشتیبانی از همزیستی موقت کلید قدیم و جدید.

🚫

۲. ابطال فوری (Revocation)

امکان Revoke کردن ApiKey قبل از انقضای طبیعی، برای موارد اضطراری مانند نشت کلید.

🌐

۳. IP Whitelist

محدودسازی استفاده از ApiKey به IPهای مشخص برای کاهش ریسک سوءاستفاده در صورت نشت.

🔒

۴. Rate Limiting

محدودیت تعداد درخواست در واحد زمان برای هر ApiKey جهت جلوگیری از سوءاستفاده و حملات DoS.

📊 ب) نیازمندی‌های مدیریتی

📝

۵. Metadata و توضیحات

ذخیره نام برنامه، توضیحات، نام مالک و اطلاعات تماس برای هر ApiKey.

👤

۶. Owner Assignment

تخصیص هر ApiKey به یک کاربر یا سازمان مشخص برای مدیریت متمرکز و حسابرسی.

📈

۷. Audit Log

ثبت کامل لاگ استفاده از هر ApiKey شامل زمان، IP، Endpoint و نتیجه درخواست.

🏷️

۸. Scope Management

تعریف دقیق Scopeهای مجاز برای هر ApiKey (مانند read, write, admin) مشابه OAuth2.

⚙️ ج) نیازمندی‌های عملیاتی

🔔

۹. Notification System

اطلاع‌رسانی به مالک قبل از انقضای ApiKey (مثلاً ۷ روز قبل) از طریق Email/SMS.

🔄

۱۰. Auto-Extension

امکان تمدید خودکار ApiKey در صورت فعال بودن برنامه (اختیاری).

📦

۱۱. Bulk Operations

امکان ایجاد، ابطال یا تمدید دسته‌ای ApiKeyها برای مدیریت در مقیاس بزرگ.

🗂️

۱۲. Tagging & Categorization

دسته‌بندی ApiKeyها بر اساس نوع برنامه (Mobile, Web, IoT, Third-party) برای گزارش‌گیری.

⚠️ پیشنهاد مهم: پیاده‌سازی حداقل موارد ۱ تا ۴ (چرخش کلید، ابطال فوری، IP Whitelist، Rate Limiting) برای یک سیستم تولیدی (Production) الزامی است. سایر موارد بر اساس اولویت کسب‌وکار در فازهای بعدی قابل اضافه شدن هستند.
۴

طراحی معماری و مدل داده

Architecture Design & Data Model

🏗️ معماری کلی سیستم

لایه API
ApplicationController — مدیریت برنامه‌ها و ApiKeyها (CRUD)
لایه سرویس
XApplicationManager — منطق تجاری، تولید کلید، مدیریت چرخه حیات
لایه زیرساخت
XApiKeyValidator + XApiKeyAuthProvider — اعتبارسنجی و احراز هویت
لایه داده
XApplication + XApiKey + XApiKeyUsageLog — Entityهای جدید

📋 مدل‌های داده پیشنهادی

// Entity: XApplication (برنامه کاربردی)
public class XApplication : XBaseGuidIDEntity
{
    [Required][StringLength(255)]
    public string Name { get; set; }
    
    [StringLength(1000)]
    public string Description { get; set; }
    
    [Required][StringLength(255)]
    public string OwnerId { get; set; } // XUser.Id
    
    public XApplicationType Type { get; set; }
    public bool IsActive { get; set; } = true;
    public DateTime CreatedOn { get; set; }
    public DateTime UpdatedAt { get; set; }
    
    // Navigation
    public virtual ICollection<XApiKey> ApiKeys { get; set; }
}
// Entity: XApiKey (کلید دسترسی)
public class XApiKey : XBaseGuidIDEntity
{
    [Required]
    public Guid ApplicationId { get; set; }
    
    [Required][StringLength(512)]
    public string KeyHash { get; set; } // SHA256 of ApiKey
    
    [StringLength(100)]
    public string KeyPrefix { get; set; } // "xapp_abc123..."
    
    [Required]
    public DateTime ExpiresAt { get; set; }
    
    public DateTime CreatedOn { get; set; }
    public DateTime? LastUsedAt { get; set; }
    public DateTime? RevokedAt { get; set; }
    public string RevokedBy { get; set; }
    
    public bool IsRevoked => RevokedAt.HasValue;
    public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
    public bool IsActive => !IsRevoked && !IsExpired;
    
    // Scope & Restrictions
    [StringLength(1000)]
    public string AllowedScopes { get; set; } // JSON array
    
    [StringLength(2000)]
    public string AllowedIPs { get; set; } // Comma-separated
    
    public int RateLimitPerMinute { get; set; } = 60;
    
    // Navigation
    public virtual XApplication Application { get; set; }
}
// Entity: XApiKeyUsageLog (لاگ استفاده)
public class XApiKeyUsageLog : XBaseLongIDEntity
{
    public Guid ApiKeyId { get; set; }
    public string Endpoint { get; set; }
    public string HttpMethod { get; set; }
    public string ClientIP { get; set; }
    public int StatusCode { get; set; }
    public DateTime RequestedOn { get; set; }
    public long ResponseTimeMs { get; set; }
}

🔢 Enumهای مورد نیاز

public enum XApplicationType
{
    Web, Mobile, Desktop, IoT, ThirdParty, Service
}

public enum XApiKeyScope
{
    [StringValue("read")] Read,
    [StringValue("write")] Write,
    [StringValue("admin")] Admin,
    [StringValue("manage")] Manage
}
۵

مرحله ۱: پیاده‌سازی در xIds

Phase 1: Implementation in xIds Module

📌 گام ۱.۱: افزودن تنظیمات قابل پیکربندی

در کلاس XIdentityConfiguration (موجود در xIdentityModels)، بخش جدید برای تنظیمات ApiKey اضافه می‌شود:

// File: xIdentityModels/Configurations/XIdentityConfiguration.cs
public class XIdentityConfiguration
{
    // ... existing properties ...
    
    public XApiKeyConfiguration ApiKey { get; set; } = new();
}

public class XApiKeyConfiguration
{
    // Default expiration in minutes (e.g., 43200 = 30 days)
    public int DefaultExpirationMinutes { get; set; } = 43200;
    
    // Maximum allowed expiration (e.g., 525600 = 1 year)
    public int MaxExpirationMinutes { get; set; } = 525600;
    
    // Maximum ApiKeys per Application
    public int MaxKeysPerApplication { get; set; } = 10;
    
    // Notification before expiration (minutes)
    public int NotifyBeforeMinutes { get; set; } = 10080; // 7 days
    
    // Enable audit logging
    public bool EnableAuditLog { get; set; } = true;
    
    // Default rate limit (requests per minute)
    public int DefaultRateLimit { get; set; } = 60;
}

📌 گام ۱.۲: افزودن DbContext و Migration

در XIdentityDbContext، DbSetهای جدید اضافه می‌شوند:

// File: xIds/DbContext/XIdentityDbContext.cs
public class XIdentityDbContext : IdentityDbContext
{
    // ... existing DbSets ...
    
    public DbSet<XApplication> Applications { get; set; }
    public DbSet<XApiKey> ApiKeys { get; set; }
    public DbSet<XApiKeyUsageLog> ApiKeyUsageLogs { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        // Configure XApplication
        modelBuilder.Entity<XApplication>(e => {
            e.HasIndex(a => a.Name).IsUnique();
            e.HasMany(a => a.ApiKeys)
             .WithOne(k => k.Application)
             .HasForeignKey(k => k.ApplicationId);
        });
        
        // Configure XApiKey
        modelBuilder.Entity<XApiKey>(e => {
            e.HasIndex(k => k.KeyHash).IsUnique();
            e.HasIndex(k => k.ExpiresAt);
        });
    }
}

📌 گام ۱.۳: پیاده‌سازی XApplicationManager

یک کلاس جدید برای مدیریت برنامه‌ها و ApiKeyها مشابه الگوی XIdentityManager موجود:

// File: xIds/Interfaces/IXApplicationManager.cs
public interface IXApplicationManager
{
    // Application CRUD
    Task<XApplicationDto> CreateApplication(XApplicationDto item, string ownerId);
    Task<XApplicationDto> UpdateApplication(Guid id, XApplicationDto item);
    Task<bool> DeleteApplication(Guid id);
    Task<XApplicationDto> GetApplication(Guid id);
    Task<IEnumerable<XApplicationDto>> GetOwnerApplications(string ownerId);
    
    // ApiKey Management
    Task<XApiKeyCreationResult> CreateApiKey(
        Guid applicationId,
        TimeSpan? expiration = null,
        IEnumerable<string> scopes = null,
        IEnumerable<string> allowedIPs = null,
        int? rateLimit = null);
    
    Task<bool> RevokeApiKey(Guid apiKeyId, string revokedBy);
    Task<XApiKeyDto> RotateApiKey(Guid apiKeyId, TimeSpan? newExpiration = null);
    Task<IEnumerable<XApiKeyDto>> GetApplicationApiKeys(Guid applicationId);
    
    // Validation
    Task<XApiKeyValidationResult> ValidateApiKey(string apiKey, string clientIP);
}

📌 گام ۱.۴: تولید امن ApiKey

الگوی تولید کلید باید از نظر رمزنگاری امن باشد:

// File: xIds/Helpers/XApiKeyGenerator.cs
public static class XApiKeyGenerator
{
    public static (string plainKey, string hash, string prefix) Generate()
    {
        // Generate 32 bytes of cryptographically secure random data
        var randomBytes = new byte[32];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomBytes);
        }
        
        // Format: xapp_{base64url}
        var plainKey = $"xapp_{Convert.ToBase64String(randomBytes)
            .Replace("+", "-").Replace("/", "_").TrimEnd('=')}";
        
        // Hash with SHA256 for storage (never store plain key)
        using (var sha256 = SHA256.Create())
        {
            var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plainKey));
            var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
            
            // Prefix for quick identification (first 12 chars)
            var prefix = plainKey.Substring(0, 16) + "...";
            
            return (plainKey, hash, prefix);
        }
    }
    
    public static bool Verify(string plainKey, string storedHash)
    {
        using (var sha256 = SHA256.Create())
        {
            var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plainKey));
            var computedHash = BitConverter.ToString(hashBytes)
                .Replace("-", "").ToLower();
            return computedHash == storedHash;
        }
    }
}
✅ ویژگی‌های امنیتی این طراحی:
  • کلید ApiKey هرگز به‌صورت Plain Text ذخیره نمی‌شود (فقط Hash)
  • استفاده از RandomNumberGenerator برای تولید امن
  • پیشوند xapp_ برای شناسایی سریع نوع کلید
  • Prefix کوتاه برای نمایش در UI بدون افشای کلید کامل
۶

مرحله ۲: سرویس اعتبارسنجی ApiKey

Phase 2: ApiKey Validation Service

📌 گام ۲.۱: طراحی XApiKeyValidator

این سرویس مسئولیت اعتبارسنجی کامل ApiKey را بر عهده دارد:

// File: xIds/Validators/XApiKeyValidator.cs
public class XApiKeyValidator : IXApiKeyValidator
{
    private readonly XIdentityDbContext _dbContext;
    private readonly IXIdentityConfiguration _config;
    private readonly IMemoryCache _cache; // For rate limiting
    
    public async Task<XApiKeyValidationResult> ValidateAsync(
        string apiKey, string clientIP, string requiredScope = null)
    {
        var result = new XApiKeyValidationResult();
        
        // Step 1: Compute hash of provided key
        var hash = XApiKeyGenerator.ComputeHash(apiKey);
        
        // Step 2: Lookup in database
        var keyEntity = await _dbContext.ApiKeys
            .Include(k => k.Application)
            .FirstOrDefaultAsync(k => k.KeyHash == hash);
        
        if (keyEntity == null)
        {
            result.IsValid = false;
            result.Error = XException.InvalidApiKey;
            return result;
        }
        
        // Step 3: Check expiration
        if (keyEntity.IsExpired)
        {
            result.IsValid = false;
            result.Error = XException.ApiKeyExpired;
            return result;
        }
        
        // Step 4: Check revocation
        if (keyEntity.IsRevoked)
        {
            result.IsValid = false;
            result.Error = XException.ApiKeyRevoked;
            return result;
        }
        
        // Step 5: Check application active
        if (!keyEntity.Application.IsActive)
        {
            result.IsValid = false;
            result.Error = XException.ApplicationDisabled;
            return result;
        }
        
        // Step 6: Check IP whitelist
        if (!string.IsNullOrEmpty(keyEntity.AllowedIPs))
        {
            var allowedIPs = keyEntity.AllowedIPs.Split(',');
            if (!allowedIPs.Contains(clientIP))
            {
                result.IsValid = false;
                result.Error = XException.IPNotAllowed;
                return result;
            }
        }
        
        // Step 7: Check rate limit
        if (!CheckRateLimit(keyEntity.Id, keyEntity.RateLimitPerMinute))
        {
            result.IsValid = false;
            result.Error = XException.RateLimitExceeded;
            return result;
        }
        
        // Step 8: Check scope (if required)
        if (!string.IsNullOrEmpty(requiredScope))
        {
            var scopes = JsonConvert.DeserializeObject<List<string>>(keyEntity.AllowedScopes);
            if (!scopes.Contains(requiredScope))
            {
                result.IsValid = false;
                result.Error = XException.InsufficientScope;
                return result;
            }
        }
        
        // Step 9: Update LastUsedAt
        keyEntity.LastUsedAt = DateTime.UtcNow;
        await _dbContext.SaveChangesAsync();
        
        // Step 10: Build success result
        result.IsValid = true;
        result.ApplicationId = keyEntity.ApplicationId;
        result.OwnerId = keyEntity.Application.OwnerId;
        result.Scopes = JsonConvert.DeserializeObject<List<string>>(keyEntity.AllowedScopes);
        
        return result;
    }
}

📌 گام ۲.۲: پیاده‌سازی Rate Limiter

private bool CheckRateLimit(Guid apiKeyId, int limitPerMinute)
{
    var cacheKey = $"ratelimit:{apiKeyId}";
    var currentCount = _cache.GetOrCreate(cacheKey, entry => {
        entry.SlidingExpiration = TimeSpan.FromMinutes(1);
        return 0;
    });
    
    if (currentCount >= limitPerMinute)
        return false;
    
    _cache.Set(cacheKey, currentCount + 1, TimeSpan.FromMinutes(1));
    return true;
}
۷

مرحله ۳: سرویس احراز هویت مبتنی بر ApiKey

Phase 3: ApiKey Authentication Service

📌 گام ۳.۱: ایجاد Authentication Handler

برای یکپارچگی با ASP.NET Core Authentication، یک Handler جدید ایجاد می‌کنیم:

// File: xIdentityService/Authentication/XApiKeyAuthenticationHandler.cs
public class XApiKeyAuthenticationHandler : AuthenticationHandler<XApiKeyAuthenticationOptions>
{
    public const string AuthenticationScheme = "XApiKey";
    public const string HeaderName = "X-Api-Key";
    
    private readonly IXApiKeyValidator _validator;
    
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Extract ApiKey from header
        if (!Request.Headers.ContainsKey(HeaderName))
            return AuthenticateResult.NoResult();
        
        var apiKey = Request.Headers[HeaderName].ToString();
        var clientIP = Request.HttpContext.Connection.RemoteIpAddress?.ToString();
        
        // Validate ApiKey
        var validationResult = await _validator.ValidateAsync(apiKey, clientIP);
        
        if (!validationResult.IsValid)
            return AuthenticateResult.Fail(validationResult.Error.Message);
        
        // Build ClaimsPrincipal
        var claims = new[]
        {
            new Claim(ClaimTypes.Name, validationResult.OwnerId),
            new Claim("application_id", validationResult.ApplicationId.ToString()),
            new Claim("auth_type", "apikey"),
            new Claim(JwtClaimTypes.Scope, "apikey"),
        };
        
        // Add scope claims
        var scopeClaims = validationResult.Scopes
            .Select(s => new Claim(JwtClaimTypes.Scope, s));
        
        var identity = new ClaimsIdentity(claims.Union(scopeClaims), AuthenticationScheme);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, AuthenticationScheme);
        
        return AuthenticateResult.Success(ticket);
    }
}

📌 گام ۳.۲: ثبت در Startup

// File: xApi/Startup.cs - ConfigureServices
services.AddAuthentication(options => {
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(/* existing config */)
.AddScheme<XApiKeyAuthenticationOptions, XApiKeyAuthenticationHandler>(
    XApiKeyAuthenticationHandler.AuthenticationScheme,
    options => { });

// Authorization policies for ApiKey
services.AddAuthorization(options => {
    options.AddPolicy("ApiKeyAccess", policy => {
        policy.AddAuthenticationSchemes(XApiKeyAuthenticationHandler.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
    });
    
    // Scope-based policies
    options.AddPolicy("ApiKeyRead", policy => {
        policy.AddAuthenticationSchemes(XApiKeyAuthenticationHandler.AuthenticationScheme);
        policy.RequireClaim(JwtClaimTypes.Scope, "read", "write", "admin");
    });
});

📌 گام ۳.۳: استفاده در Controllerها

// Example: Supporting both JWT and ApiKey authentication
[Authorize(Policy = "ApiKeyRead")]
public class DataController : ControllerBase
{
    [HttpGet("items")]
    public async Task<ActionResult> GetItems()
    {
        // Works with both JWT Bearer and X-Api-Key header
        var userId = User.Identity.Name;
        // ...
    }
}
۸

مرحله ۴: API Endpoints

Phase 4: API Endpoints Design

📌 طراحی Controller

// File: xIds/Controllers/ApplicationController.cs
[ApiController]
[Route("api/v1/applications")]
[Authorize(Policy = XPolicies.EnabledUser)]
public class ApplicationController : XIBaseController
{
    // Application CRUD
    [HttpPost]
    public async Task<ActionResult<XApplicationDto>> Create(XApplicationDto model);
    
    [HttpGet]
    public async Task<ActionResult<IEnumerable<XApplicationDto>>> GetMyApplications();
    
    [HttpPut("{id}")]
    public async Task<ActionResult> Update(Guid id, XApplicationDto model);
    
    [HttpDelete("{id}")]
    public async Task<ActionResult> Delete(Guid id);
    
    // ApiKey Management
    [HttpPost("{appId}/apikeys")]
    public async Task<ActionResult<XApiKeyCreationResponse>> CreateApiKey(
        Guid appId, XCreateApiKeyRequest request);
    
    [HttpGet("{appId}/apikeys")]
    public async Task<ActionResult<IEnumerable<XApiKeyDto>>> GetApiKeys(Guid appId);
    
    [HttpPost("apikeys/{id}/revoke")]
    public async Task<ActionResult> RevokeApiKey(Guid id);
    
    [HttpPost("apikeys/{id}/rotate")]
    public async Task<ActionResult<XApiKeyCreationResponse>> RotateApiKey(Guid id);
}

📋 DTOهای مورد نیاز

DTO کاربرد فیلدهای کلیدی
XApplicationDto نمایش برنامه Id, Name, Description, Type, IsActive, CreatedOn
XCreateApplicationRequest درخواست ساخت برنامه Name, Description, Type
XApiKeyDto نمایش ApiKey (بدون کلید کامل) Id, KeyPrefix, ExpiresAt, LastUsedAt, Scopes, IsActive
XCreateApiKeyRequest درخواست ساخت ApiKey ExpirationMinutes, Scopes, AllowedIPs, RateLimit
XApiKeyCreationResponse پاسخ ساخت (فقط یکبار کلید کامل) ApiKey (plain), ExpiresAt, KeyPrefix
🔴 نکته امنیتی بسیار مهم: کلید ApiKey به‌صورت Plain Text فقط یکبار در پاسخ XApiKeyCreationResponse به کاربر نمایش داده می‌شود. پس از آن، فقط KeyPrefix قابل مشاهده است و کاربر در صورت فراموشی کلید، باید آن را Rotate کند.
۹

جریان عملیات و سناریوها

Operational Flows & Scenarios

🔄 سناریوی ۱: ایجاد برنامه و دریافت ApiKey

۱
کاربر درخواست ایجاد برنامه را ارسال می‌کند POST /api/v1/applications با payload شامل نام، توضیحات و نوع برنامه
۲
سیستم برنامه را ایجاد می‌کند ذخیره XApplication با OwnerId = کاربر فعلی
۳
کاربر درخواست ApiKey می‌کند POST /api/v1/applications/{appId}/apikeys با تنظیمات انقضا و scope
۴
سیستم ApiKey تولید و برمی‌گرداند کلید Plain Text فقط یکبار نمایش داده می‌شود، Hash در DB ذخیره می‌شود

🔄 سناریوی ۲: استفاده از ApiKey در درخواست API

۱
کلاینت درخواست API را ارسال می‌کند Header: X-Api-Key: xapp_abc123...
۲
Authentication Handler کلید را استخراج می‌کند XApiKeyAuthenticationHandler فعال می‌شود
۳
اعتبارسنجی کامل انجام می‌شود بررسی انقضا، ابطال، IP whitelist، rate limit، scope
۴
ClaimsPrincipal ساخته می‌شود با OwnerId، ApplicationId و Scopes
۵
درخواست به Controller هدایت می‌شود Authorization policy بررسی و پاسخ ارسال می‌گردد

🔄 سناریوی ۳: چرخش کلید (Key Rotation)

۱
کاربر درخواست چرخش کلید را می‌دهد POST /api/v1/applications/apikeys/{id}/rotate
۲
کلید جدید تولید می‌شود کلید قدیمی برای مدت grace period (مثلاً ۱ ساعت) فعال می‌ماند
۳
کلید جدید به کاربر داده می‌شود کاربر کلید جدید را در برنامه خود جایگزین می‌کند
۴
پس از grace period، کلید قدیمی منقضی می‌شود Background job کلیدهای قدیمی را غیرفعال می‌کند
۱۰

الگوهای امنیتی و ملاحظات

Security Patterns & Considerations
الگوی امنیتی پیاده‌سازی اهمیت
Hashing کلید SHA256 - کلید Plain Text هرگز ذخیره نمی‌شود حیاتی
HTTPS Only ApiKey فقط از طریق HTTPS قابل ارسال است حیاتی
Rate Limiting بر اساس ApiKey و IP با MemoryCache بالا
IP Whitelist محدودسازی به IPهای مجاز بالا
Scope Management هر ApiKey scope مشخصی دارد بالا
Audit Logging ثبت تمام استفاده‌ها در XApiKeyUsageLog متوسط
Expiration مدت زمان محدود با قابلیت تمدید بالا
Revocation امکان ابطال فوری در صورت نشت حیاتی
Key Rotation چرخش بدون downtime با grace period متوسط
One-time Display کلید فقط یکبار به کاربر نمایش داده می‌شود بالا
🎯 یکپارچگی با OAuth2: این طراحی با الگوی OAuth2 Client Credentials Grant همخوانی کامل دارد. در واقع هر Application معادل یک OAuth2 Client و هر ApiKey معادل یک Client Secret است. در آینده می‌توان به‌راحتی این سیستم را به IdentityServer4 متصل کرد و از token endpoint آن برای تبدیل ApiKey به JWT استفاده نمود.

🔐 ملاحظات امنیتی تکمیلی

  • ذخیره‌سازی: کلید ApiKey هرگز در Log یا Response بعد از ساخت ذخیره نمی‌شود
  • انتقال: فقط از طریق Header (X-Api-Key)، نه URL Query
  • طول کلید: حداقل ۲۵۶ بیت (۳۲ بایت) entropy
  • Brute Force Protection: پس از ۵ تلاش ناموفق، IP برای ۱۵ دقیقه block می‌شود
  • CORS: برای Endpoints حساس، CORS باید به‌دقت پیکربندی شود
۱۱

جمع‌بندی و گام‌های بعدی

Summary & Next Steps

✅ خلاصه طراحی

این مستند یک راهکار کامل برای مدیریت ApiKey در پلتفرم xDashboard ارائه می‌دهد که شامل موارد زیر است:

  • مدل داده کامل با ۳ Entity جدید (XApplication, XApiKey, XApiKeyUsageLog)
  • پیاده‌سازی امن تولید و ذخیره‌سازی کلید با SHA256
  • Authentication Handler یکپارچه با ASP.NET Core
  • سیستم Rate Limiting و IP Whitelist
  • امکان چرخش کلید بدون downtime
  • سیستم Audit Log کامل
  • یکپارچگی با OAuth2 برای توسعه آینده

📋 گام‌های اجرایی پیشنهادی

فاز فعالیت زمان تخمینی
۱ طراحی و تصویب مدل داده + Migration ۱ روز
۲ پیاده‌سازی XApplicationManager + XApiKeyGenerator ۲ روز
۳ پیاده‌سازی XApiKeyValidator + Rate Limiter ۱ روز
۴ پیاده‌سازی Authentication Handler ۱ روز
۵ طراحی و پیاده‌سازی API Endpoints ۲ روز
۶ تست امنیتی و Unit Test ۲ روز
۷ مستندسازی API و راهنمای کلاینت ۱ روز
مجموع زمان تخمینی ۱۰ روز کاری

🎯 توصیه نهایی

این طراحی با رعایت کامل اصول امنیتی و انطباق با الگوهای موجود پروژه (IdentityServer4, XPolicies, XValidationProvider) آماده پیاده‌سازی است. پیشنهاد می‌شود فاز اول با حداقل نیازمندی‌های امنیتی (Hashing, HTTPS, Expiration, Revocation) شروع شود و قابلیت‌های پیشرفته (IP Whitelist, Rate Limiting, Audit Log) در فازهای بعدی اضافه گردند.

💎 ارزش پیشنهادی:
امنیت سطح Enterprise + یکپارچگی کامل با OAuth2 + مقیاس‌پذیری بالا + قابلیت ردیابی و حسابرسی کامل
📞 گام بعدی: در صورت تأیید این طراحی، می‌توانیم پیاده‌سازی را با فاز ۱ (مدل داده و Migration) شروع کنیم. کدها دقیقاً مطابق الگوهای موجود پروژه (Partial Classes برای Manager، Validation با GroupValidationBuilder، و Exception Handling با XException) نوشته خواهند شد.
این مستند محرمانه بوده و صرفاً جهت طراحی و پیاده‌سازی داخلی تهیه شده است. هرگونه کپی‌برداری یا افشا بدون اجازه کتبی ممنوع است.